#include <QtXml>
#include <QMessageBox>
#include <QDateTime>
#include "GlobPref.h"
#include "DataModel.h"
#include "WinMain.h"
#include "Request.h"
#include "XmlHelper.h"
#include "XmlSrvHandler.h"
#include "scImpl.h"

#define FCT "XmlSrvHandler::XmlSrvHandler"
XmlSrvHandler::XmlSrvHandler(DataTables & dataTables, bool isBootTrans) :
	dm(0), dts(dataTables), bootTrans(isBootTrans), responseTime(0),
	activeDomain(DOMAIN_UNDEF), activeTbl(PK_UNDEF), activeRequest(0)
{
	ctrc(Trc::WORK,APP,FCT)<<"DataTables "<<&dts<<endl;
	dm=DataModel::get();
	tableIt=dts.get<DataTable::PkTag>().end();
}

#undef FCT
#define FCT "XmlSrvHandler::~XmlSrvHandler"
XmlSrvHandler::~XmlSrvHandler()
{
}

#undef FCT
#define FCT "XmlSrvHandler::startDocument"
bool XmlSrvHandler::startDocument()
{
	ctrc(Trc::LOOP,APP,FCT)<<endl;
	scpath.push_back(SCT_UNDEF);
	return true;
}

#undef FCT
#define FCT "XmlSrvHandler::endDocument"
bool XmlSrvHandler::endDocument()
{
	ctrc(Trc::LOOP,APP,FCT)<<endl;
	scpath.pop_back();
	if(scpath.size())
		ctrc(Trc::FATAL,APP,FCT)<<"malformed document"<<endl;
	return true;
}

#undef FCT
#define FCT "XmlSrvHandler::startElement"
bool XmlSrvHandler::startElement(const QString & namespaceURI, const QString & localName, const QString & qName, const QXmlAttributes & atts)
{
	if(Trc::inst()->doTrace(Trc::INNERLOOP,APP,FCT))
	{
		*(Trc::inst())<<Trc::baseInfo(APP,FCT).c_str()<<"namespaceURI "<<namespaceURI.toStdString().c_str()<<
			" localName "<<localName.toStdString().c_str()<<" qName "<<qName.toStdString().c_str()<<endl;
		for(int i=0; i<atts.count(); ++i)
			*(Trc::inst())<<Trc::baseInfo(APP,FCT).c_str()<<"attr "<<i<<": localName "<<atts.localName(i).toStdString().c_str()<<
				" qName "<<atts.qName(i).toStdString().c_str()<<" value "<<atts.value(i).toStdString().c_str()<<endl;
	}
	DataModel * dm(DataModel::get());
	switch(scpath.back())
	{
	case SCT_UNDEF: // doc root
		if(qName=="sybil-pm")
		{
			QString rts(atts.value(QString("timestamp_UTC")));
			if(rts.isEmpty())
				return false;
			responseTime=QDateTime::fromString(rts).toTime_t();
			scpath.push_back(SCT_DOC); // now at doc level
			return true;
		}
		return false;
	case SCT_DOC:
	{ // a sequence of domains
		activeDomain=XmlHelper::domainText2Domain(qName);
		if(activeDomain==DOMAIN_UNDEF)
		{
			ctrc(Trc::FATAL,APP,FCT)<<"qName "<<qName.toStdString().c_str()<<" can't be resolved to a domain"<<endl;
			return false;
		}
		scpath.push_back(SCT_DOMAIN); // now inside a domain
		return true;
	}
	case SCT_DOMAIN: // table / entity_list
	{
		if(qName=="entity_list")
		{
			activeTbl=dm->text2EntityId(atts.value(QString("tech_name")));
			scpath.push_back(SCT_ENTITYLIST); // now inside a e-list
			if(!activeTbl)
			{
				ctrc(Trc::FATAL,APP,FCT)<<"qName "<<atts.value(QString("tech_name")).toStdString().c_str()<<" can't be resolved to an entity"<<endl;
				return false;
			}
			if(bootTrans)
			{
				mts.insert(MiniTables::value_type(activeTbl, MiniObjects()));
				return true;
			}
			DTsPkIdx & pks(dts.get<DataTable::PkTag>());
			tableIt=pks.find(activeTbl);
			if(tableIt==pks.end())
			{ // XML is OK but we don't have the table at all
				throw new AppException(FCT "can't find table", true);
			}
			tableIt->getPayload().modifyTable(atts);
			return true;
		}
		ctrc(Trc::FATAL,APP,FCT)<<"qName "<<qName.toStdString().c_str()<<" is not a known element of SCT_DOMAIN"<<endl;
		return false;
	}
	case SCT_ENTITYLIST:
	case SCT_ENTITYGROUP:
	{
		if(qName=="entity")
		{
			scpath.push_back(SCT_ENTITY);
			if(bootTrans)
			{
				MiniObject mo;
				for(int i(0); i<atts.length(); ++i)
					mo.push_back(atts.value(QString("_")+QString::number(i)));
				mts.find(activeTbl)->second.push_back(mo);
				return true;
			}
			int idx(atts.index(QString("reason")));
			QMessageBox::StandardButton answer(QMessageBox::Yes);
			if(idx>=0 && atts.value(idx)=="update_conflict")
				answer=QMessageBox::question(GlobPref::get()->getMainWindow(), QObject::tr("Update Conflict", "XmlSrvHandler"),
					QObject::tr("The server has already a newer version of this data set. May I discard your "
						"changes and load the new values? (Even if you decline your data will not be saved to "
						"the server. Copy-and-paste it to an external location, restart your client and edit again.)", "XmlSrvHandler"),
					QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
			if(idx>=0 && atts.value(idx)=="timeline_conflict")
				answer=QMessageBox::question(GlobPref::get()->getMainWindow(), QObject::tr("Time-line Conflict", "XmlSrvHandler"),
					QObject::tr("The change would create an inconsistent time-line with data-sets overlapping in time. May I discard your "
						"changes and re-load the old values? (Even if you decline your data will not be saved to "
						"the server. Edit it to be consistent and restart your client.)", "XmlSrvHandler"),
						QMessageBox::Yes | QMessageBox::No , QMessageBox::Yes);
					// TODO feature - timeline reconciliation dialog
			if(answer==QMessageBox::Yes)
			{
				ctrc(Trc::WORK,APP,FCT)<<"updating table "<<tableIt->getPk()<<" ("<<&(*tableIt)<<")"<<endl;
				tableIt->getPayload().modifyData(atts);
			}
			else
				ctrc(Trc::WORK,APP,FCT)<<"updating for table "<<tableIt->getPk()<<" ("<<&(*tableIt)<<") rejected"<<endl;
			return true;
		}
		else if(qName=="entity_group")
		{
			scpath.push_back(SCT_ENTITYGROUP);
			// TODO feature - timeline reconciliation dialog
			return true;
		}
		else if(qName=="scope")
		{
			scpath.push_back(SCT_SCOPE);
			if(bootTrans)
				return true;
			ctrc(Trc::WORK,APP,FCT)<<"updating table cache "<<tableIt->getPk()<<" ("<<&(*tableIt)<<")"<<endl;
			if(tableIt==dts.get<DataTable::PkTag>().end())
				return false;
			activeRequest=new Request(tableIt->getPk(), atts);
			return true;
		}
		ctrc(Trc::FATAL,APP,FCT)<<"qName "<<qName.toStdString().c_str()<<" is not a known element of SCT_ENTITYLIST"<<endl;
		return false;
	}
	case SCT_ENTITY:
		ctrc(Trc::FATAL,APP,FCT)<<"qName "<<qName.toStdString().c_str()<<" is not a known element of SCT_ENTITY"<<endl;
		return false;
	case SCT_SCOPE:
		if(qName=="key")
		{
			scpath.push_back(SCT_SCOPEKEY);
			if(bootTrans)
				return true;
			PkType key(atts.value(QString("id")).toLongLong());
			if(!activeRequest || !key)
				return false;
			activeRequest->addKey(key);
			return true;
		}
		ctrc(Trc::FATAL,APP,FCT)<<"qName "<<qName.toStdString().c_str()<<" is not a known element of SCT_SCOPE"<<endl;
		return false;
	case SCT_SCOPEKEY:
		ctrc(Trc::FATAL,APP,FCT)<<"qName "<<qName.toStdString().c_str()<<" is not a known element of SCT_SCOPEKEY"<<endl;
		return false;
	default:
		ctrc(Trc::WORK,APP,FCT)<<"sctype "<<scpath.back()<<" not handled"<<endl;
		return false;
	}
	return false;
}

#undef FCT
#define FCT "XmlSrvHandler::endElement"
bool XmlSrvHandler::endElement(const QString & namespaceURI, const QString & localName, const QString & qName )
{
	ctrc(Trc::INNERLOOP,APP,FCT)<<"namespaceURI "<<namespaceURI.toStdString().c_str()<<
		" localName "<<localName.toStdString().c_str()<<" qName "<<qName.toStdString().c_str()<<endl;
	switch(scpath.back())
	{
	case SCT_DOC:
		if(bootTrans)
		{ // during startup we have to unfold the meta-info
			MiniObjects & tbls(mts.find(EID_M_ENTITIES)->second);
			MiniObjects & atrbts(mts.find(EID_M_ATTRS)->second);
			for(MiniObjects::iterator tblit(tbls.begin()); tblit!=tbls.end(); ++tblit)
			{ // each table
				PkType myTable(tblit->at(M_ENTITIES_PK).toLongLong());
				ctrc(Trc::WORK,APP,FCT)<<"creating table "<<myTable<<endl;
				ColDescs attrs;
				for(MiniObjects::iterator atrbtit(atrbts.begin()); atrbtit!=atrbts.end(); ++atrbtit)
				{ // find attributes
					if(atrbtit->at(M_ATTRS_FPKENT).toLongLong()==myTable)
						attrs.push_back(ColDesc(atrbtit->at(M_ATTRS_TNAME), atrbtit->at(M_ATTRS_NAME),
							atrbtit->at(M_ATTRS_FUNCTION).toInt(), atrbtit->at(M_ATTRS_TARGET).toLongLong(),
							atrbtit->at(M_ATTRS_DTYPE).toInt()));
				}
				dts.insert(DataTable(myTable,
					tblit->at(M_ENTITIES_TNAME).toStdString().c_str(),
					tblit->at(M_ENTITIES_NAME),
					tblit->at(M_ENTITIES_TRNAME).toStdString().c_str(),
					tblit->at(M_ENTITIES_RNAME),
					(DataDomain)tblit->at(M_ENTITIES_DOMAIN).toInt(), &attrs));
			}
			return true;
		}
		break;
	case SCT_DOMAIN:
		activeDomain=DOMAIN_UNDEF;
		break;
	case SCT_ENTITYLIST: // table / entity_list
		activeTbl=PK_UNDEF;
		tableIt=dts.get<DataTable::PkTag>().end();
		break;
	case SCT_ENTITYGROUP:
		break;
	case SCT_ENTITY:
		break;
	case SCT_SCOPE:
		if(bootTrans)
			break;
		if(!activeRequest)
			return false;
		tableIt->getPayload().modifyCache(activeRequest, responseTime);
		delete activeRequest;
		activeRequest=0;
		break;
	default:
		ctrc(Trc::WORK,APP,FCT)<<"sctype "<<scpath.back()<<" not handled"<<endl;
		scpath.pop_back();
		return false;
	}
	scpath.pop_back();
	return true;
}

#undef FCT
#define FCT "XmlSrvHandler::warning"
bool XmlSrvHandler::warning(const QXmlParseException & exception)
{
	ctrc(Trc::WORK,APP,FCT)<<"XML warning: "<<exception.message().toStdString().c_str()<<endl;
	return true;
}

#undef FCT
#define FCT "XmlSrvHandler::error"
bool XmlSrvHandler::error(const QXmlParseException & exception)
{
	ctrc(Trc::WORK,APP,FCT)<<"XML error: "<<exception.message().toStdString().c_str()<<endl;
	return true;
}

#undef FCT
#define FCT "XmlSrvHandler::fatalError"
bool XmlSrvHandler::fatalError(const QXmlParseException & exception)
{
	ctrc(Trc::FATAL,APP,FCT)<<"XML fatal error: "<<exception.message().toStdString().c_str()<<endl;
	return true;
}
